﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;

//This class is optimized such that it can be cloned without requiring the cloning of its members until they are going to be read.
public class ReadOnlyableDictionary<TKey,TValue> : ReadOnlyable, IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, ICollection, IDictionary, IEnumerable, IDeserializationCallback, ISerializable {
	private Dictionary<TKey,TValue> container;
	private Dictionary<TKey,TValue> clonedMembers = null;
	
	protected override ReadOnlyable GetNewInstance ()
	{
		ReadOnlyableDictionary<TKey,TValue> clone = new ReadOnlyableDictionary<TKey,TValue>();
		clone.CloneMembersOnAccess();
		return clone;
	}

	internal void CloneMembersOnAccess ()
	{
		clonedMembers = new Dictionary<TKey,TValue>();
	}

	//Copies even ReadOnlyable members without changing their ReadOnly property! If necessary, their ReadOnly property should be updated on retrieval.
	public override void CopyTo (ReadOnlyable target)
	{
		ReadOnlyableDictionary<TKey,TValue> targetDictionary = (ReadOnlyableDictionary<TKey,TValue>)target;

		foreach (KeyValuePair<TKey,TValue> pair in container)
		{
			targetDictionary.Add(pair.Key , pair.Value);
		}
	}

	private void UpdateAllClones ()
	{
		if (clonedMembers == null || clonedMembers.Count == container.Count)
		{
			return;
		}

		foreach (TKey key in container.Keys)
		{
			if (!UpdateClone(key))
			{
				//This assumes that if there is one member that is not a ReadOnlyable, there are no member ReadOnlyables.
				break;
			}
		}
	}

	private bool UpdateClone (TKey key)
	{
		if (clonedMembers == null)
		{
			return false;
		}

		TValue member = container[key];

		if (!(member is ReadOnlyable))
		{
			return false;
		}
		
		ReadOnlyable readOnlyable = member as ReadOnlyable;
		
		if (!clonedMembers.ContainsValue(member))
		{
			//An untyped var must be used because a ReadOnlyable cannot be converted to a TValue.
			object untypedMember = readOnlyable.CloneWritable();
			member = (TValue)untypedMember;
			readOnlyable = (ReadOnlyable)untypedMember;
			container[key] = member;
			clonedMembers[key] = member;

			if (ReadOnly)
			{
				readOnlyable.ReadOnly = true;
			}
		}
		return true;
	}

	public int Count
	{
		get
		{
			return container.Count;
		}
	}
	
	bool IDictionary.IsFixedSize
	{
		get
		{
			return false;
		}
	}
	
	bool IDictionary.IsReadOnly
	{
		get
		{
			return false;
		}
	}
	
	bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
	{
		get
		{
			return false;
		}
	}
	
	bool ICollection.IsSynchronized
	{
		get
		{
			return false;
		}
	}
	
	public Dictionary<TKey, TValue>.KeyCollection Keys
	{
		get
		{
			return container.Keys;
		}
	}
	
	ICollection<TKey> IDictionary<TKey, TValue>.Keys
	{
		get
		{
			return this.Keys;
		}
	}
	
	ICollection IDictionary.Keys
	{
		get
		{
			return this.Keys;
		}
	}
	
	object ICollection.SyncRoot
	{
		get
		{
			return this;
		}
	}
	
	public Dictionary<TKey, TValue>.ValueCollection Values
	{
		get
		{
			UpdateAllClones();
			return container.Values;
		}
	}
	
	ICollection<TValue> IDictionary<TKey, TValue>.Values
	{
		get
		{
			return this.Values;
		}
	}
	
	ICollection IDictionary.Values
	{
		get
		{
			return this.Values;
		}
	}
	
	//
	// Indexer
	//
	public TValue this [TKey key]
	{
		get
		{
			UpdateClone(key);
			return container[key];
		}
		set
		{
			if (ReadOnly)
			{
				LogErrorWriteToReadOnly();
				return;
			}

			if (value is ReadOnlyable)
			{
				ReadOnlyable readOnlyable = value as ReadOnlyable;
				if (readOnlyable.ReadOnly)
				{
					Debug.LogError("Cannot add a member with a ReadOnly value of '" + readOnlyable.ReadOnly + "'.");
					return;
				}
			}
			
			container[key] = value;
		}
	}
	
	object IDictionary.this[object key]
	{
		get
		{
			if (key is TKey)
			{
				return this[(TKey)key];
			}

			return default(TValue);
		}
		set
		{
			if (key is TKey && value is TValue)
			{
				this[(TKey)key] = (TValue)value;
			}
		}
	}

	//
	// Constructors
	//
	public ReadOnlyableDictionary (int capacity)
	{
		container = new Dictionary<TKey, TValue>(capacity);
	}
	
	public ReadOnlyableDictionary (IDictionary<TKey, TValue> dictionary) : this(dictionary, null)
	{
		container = new Dictionary<TKey, TValue>(dictionary);
	}
	
	public ReadOnlyableDictionary (int capacity, IEqualityComparer<TKey> comparer)
	{
		container = new Dictionary<TKey, TValue>(capacity , comparer);
	}
	
	public ReadOnlyableDictionary (IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
	{
		container = new Dictionary<TKey, TValue>(dictionary , comparer);
	}
	
	public ReadOnlyableDictionary (IEqualityComparer<TKey> comparer)
	{
		container = new Dictionary<TKey, TValue>(comparer);
	}
	
	public ReadOnlyableDictionary ()
	{
		container = new Dictionary<TKey, TValue>();
	}
	
	//
	// Methods
	//
	public void Add (TKey key, TValue value)
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return;
		}
		
		container.Add(key , value);

		if (value is ReadOnlyable)
		{
			ReadOnlyable readOnlyable = value as ReadOnlyable;

			if (readOnlyable.ReadOnly)
			{
				container.Remove(key);
				Debug.LogError("Cannot add a member with a ReadOnly value of '" + readOnlyable.ReadOnly + "'.");
				return;
			}

			if (clonedMembers != null)
			{
				clonedMembers.Add(key , value);
			}
		}
	}

	void ICollection<KeyValuePair<TKey,TValue>>.Add (KeyValuePair<TKey,TValue> pair)
	{
		Add(pair.Key , pair.Value);
	}

	void IDictionary.Add (object key , object value)
	{
		if (key is TKey && value is TValue)
		{
			Add((TKey)key , (TValue)value);
		}
	}
	
	public void Clear ()
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return;
		}
		
		container.Clear();
		clonedMembers = null;
	}
	
	public bool ContainsKey (TKey key)
	{
		return container.ContainsKey(key);
	}
	
	public bool ContainsValue (TValue value)
	{
		return container.ContainsValue(value);
	}
	
	bool ICollection<KeyValuePair<TKey,TValue>>.Contains (KeyValuePair<TKey,TValue> pair)
	{
		if (!container.ContainsKey(pair.Key))
		{
			return false;
		}

		return container[pair.Key].Equals(pair.Value);
	}
	
	bool IDictionary.Contains (object pairObj)
	{
		if (!(pairObj is KeyValuePair<TKey,TValue>))
		{
			return false;
		}

		KeyValuePair<TKey,TValue> pair = (KeyValuePair<TKey,TValue>)pairObj;
		if (!container.ContainsKey(pair.Key))
		{
			return false;
		}
		
		return container[pair.Key].Equals(pair.Value);
	}

	void ICollection.CopyTo (System.Array array, int arrayIndex)
	{
		if (arrayIndex < 0)
		{
			arrayIndex = 0;
		}
		
		if (arrayIndex >= container.Count)
		{
			return;
		}

		UpdateAllClones();
		
		TKey[] keys = new TKey[container.Count];
		container.Keys.CopyTo(keys , 0);
		for (int i = arrayIndex ; i < keys.Length ; i++)
		{
			int j = i - arrayIndex;
			TKey key = keys[i];
			KeyValuePair<TKey,TValue> pair = new KeyValuePair<TKey, TValue>(key , container[key]);
			array.SetValue(pair , j);
		}
	}
	
	void ICollection<KeyValuePair<TKey,TValue>>.CopyTo (KeyValuePair<TKey,TValue>[] array , int arrayIndex)
	{
		if (arrayIndex < 0)
		{
			arrayIndex = 0;
		}

		if (arrayIndex >= container.Count)
		{
			return;
		}
		
		UpdateAllClones();

		TKey[] keys = new TKey[container.Count];
		container.Keys.CopyTo(keys , 0);
		for (int i = arrayIndex ; i < keys.Length ; i++)
		{
			int j = i - arrayIndex;
			TKey key = keys[i];
			KeyValuePair<TKey,TValue> pair = new KeyValuePair<TKey, TValue>(key , container[key]);
			array[j] = pair;
		}
	}

	//TODO: Make sure these enumerators handle member ReadOnly values properly.
	public IEnumerator GetEnumerator ()
	{
		return container.GetEnumerator();
	}
	
	IEnumerator<KeyValuePair<TKey,TValue>> IEnumerable<KeyValuePair<TKey,TValue>>.GetEnumerator ()
	{
		return container.GetEnumerator();
	}
	
	IDictionaryEnumerator IDictionary.GetEnumerator ()
	{
		return container.GetEnumerator();
	}

	public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
	{
		container.GetObjectData(info , context);
	}
	
	public virtual void OnDeserialization (object sender)
	{
		container.OnDeserialization(sender);
	}
	
	public bool Remove (TKey key)
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return false;
		}

		if (!container.Remove(key))
		{
			return false;
		}

		if (clonedMembers != null)
		{
			clonedMembers.Remove(key);
		}
		return true;
	}
	
	void IDictionary.Remove (object key)
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return;
		}

		if (!(key is TKey))
		{
			return;
		}
		
		Remove((TKey)key);
	}
	
	bool ICollection<KeyValuePair<TKey,TValue>>.Remove (KeyValuePair<TKey,TValue> pair)
	{
		return Remove(pair.Key);
	}
	
	public bool TryGetValue (TKey key, out TValue value)
	{
		return container.TryGetValue(key , out value);
	}
}
